package cn.com.weather.nf.service.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import cn.com.weather.nf.entity.TemplateInstances;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import cn.com.weather.nf.proj.GlobalConfig;
import cn.com.weather.nf.repository.BaseRepository;
import cn.com.weather.nf.repository.RecordFileRepository;
import cn.com.weather.nf.repository.RecordFormRepository;
import cn.com.weather.nf.repository.RecordWeatherRepository;
import cn.com.weather.nf.repository.SimpleRepository;
import cn.com.weather.nf.repository.TemplateFormRepository;
import cn.com.weather.nf.repository.TemplateInstancesRepository;
import cn.com.weather.nf.repository.TemplateStyleRepository;
import cn.com.weather.nf.repository.UserInfoRepository;
import cn.com.weather.nf.repository.UserScoreRepository;
import cn.com.weather.nf.repository.WeatherOptionRepository;
import cn.com.weather.nf.service.BaseOperateService;
import cn.com.weather.nf.vo.ListResult;

@Service
@Transactional
public class BaseOperateServiceImpl
    implements BaseOperateService, ApplicationContextAware{

    private static final Logger LOGGER = LoggerFactory.getLogger(BaseOperateServiceImpl.class);

    private ApplicationContext applicationContext;

    @Autowired
    private SimpleRepository simpleRepository;

    @Autowired
    private RecordFileRepository recordFileRepository;
    @Autowired
    private RecordFormRepository recordFormRepository;
    @Autowired
    private RecordWeatherRepository recordWeatherRepository;
    @Autowired
    private TemplateFormRepository templateFormRepository;
    @Autowired
    private TemplateInstancesRepository templateInstancesRepository;
    @Autowired
    private TemplateStyleRepository templateStyleRepository;
    @Autowired
    private UserInfoRepository userInfoRepository;
    @Autowired
    private UserScoreRepository userScoreRepository;
    @Autowired
    private WeatherOptionRepository weatherOptionRepository;

    private Map<String, BaseRepository> cache;

    private static final String REPOSITORY_BEAN_SUFFIX = "Repository";

    private static class CacheHolder {
        private static Map<String, BaseRepository> cache = new HashMap<>();
        private CacheHolder() {
            // nothing
        }
    }

    private BaseRepository fromCache(String key) {

        this.cache = CacheHolder.cache;

        Field[] fields = this.getClass().getDeclaredFields();

        for (Field f : fields) {
            String fieldName = f.getName();

            if (fieldName.endsWith(REPOSITORY_BEAN_SUFFIX)) {
                try {
                    BaseRepository br = (BaseRepository) f.get(this);
                    this.cache.put(fieldName, br);
                } catch (IllegalAccessException e) {
                    LOGGER.error(GlobalConfig.LOG_EXCEPTION_PREFIX, e);
                }
            }
        }

        return cache.get(key);
    }


    private BaseRepository fromCache2(String key) {
        switch (key) {
            case "recordFileRepository" : return this.recordFileRepository;
            case "recordFormRepository" : return this.recordFormRepository;
            case "recordWeatherRepository" : return this.recordWeatherRepository;
            case "templateFormRepository" : return this.templateFormRepository;
            case "templateInstancesRepository" : return this.templateInstancesRepository;
            case "templateStyleRepository" : return this.templateStyleRepository;
            case "userInfoRepository" : return this.userInfoRepository;
            case "userScoreRepository" : return this.userScoreRepository;
            case "weatherOptionRepository" : return this.weatherOptionRepository;
            default: return null;
        }
    }



    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private String assemblyRepositoryName(String className) {
        StringBuilder beanName = new StringBuilder(className.length());

        if (StringUtils.hasText(className)) {
            beanName.append(Character.toLowerCase(className.charAt(0)))
                    .append(className.substring(1))
                    .append(REPOSITORY_BEAN_SUFFIX);

            LOGGER.debug("beanName : {} ", beanName);
        }
        return beanName.toString();
    }

    private String assemblyRepositoryName(Serializable data) {
        String className = data.getClass().getSimpleName();
        return assemblyRepositoryName(className);
    }


    @Override
    public void save(Serializable data) {

        // UserInfo -> UserInfo
        String beanName = this.assemblyRepositoryName(data);
        if (StringUtils.hasText(beanName)) {
            BaseRepository bo = this.fromCache2(beanName);
            if (bo != null) {
                bo.save(data);
            } else {
                LOGGER.error("{} is null : ", beanName);
            }

        }

    }

    @Override
    public Object findById(Class<?> classType, Serializable id) {

        String beanName = this.assemblyRepositoryName(classType.getSimpleName());
        if (StringUtils.hasText(beanName)) {
            return this.simpleRepository.findById(classType, id);
        }
        return null;
    }


    @Override
    @SuppressWarnings("unchecked")
    public ListResult findPagination(Class<?> beanClass, Integer current, Integer pageSize) {

        Integer total = this.simpleRepository.findListTotal(beanClass);
        List<?> data = this.simpleRepository.findListOrderById(beanClass, current, pageSize);


        return new ListResult.Builder()
                .setCurrent(current)
                .setPageSize(pageSize)
                .setTotal(total)
                .setRows(data)
                .build();
    }

    @Override
    public void delete(Class<?> type, Long id) {

        this.simpleRepository.delete(type, id);
    }


    public void update(Serializable data) {

        throw new UnsupportedOperationException("方法未完成");

    }


    @Override
    public ListResult findRecordFormPagination(String type, String from, Integer current, Integer pageSize,
                                               Integer instanceId, String search,
                                               long beginTime, long endTime) {
        
        Integer total = this.simpleRepository.findRecordFormListTotal(type, from, instanceId, search, beginTime, endTime);

        List<? extends Serializable> data = this.simpleRepository
                .findRecordFormListOrderById(type, from, current, pageSize, instanceId, search, beginTime, endTime);

        return new ListResult.Builder()
                .setCurrent(current)
                .setPageSize(pageSize)
                .setTotal(total)
                .setRows(data)
                .build();
    }


    @Override
    public ListResult findRecordWeatherPagination(String from, Integer current, Integer pageSize) {

        Integer total = this.simpleRepository.findRecordWeatherListTotal(from);
        List<? extends Serializable> data = this.simpleRepository.findRecordWeatherListOrderById(from, current, pageSize);

        return new ListResult.Builder()
                .setCurrent(current)
                .setPageSize(pageSize)
                .setTotal(total)
                .setRows(data)
                .build();
    }


    @Override
    public Object update(Serializable data, Long id) {
        
        Object origin = this.findById(data.getClass(), id);
        if (origin != null) {
            copyNotNullValue(data, origin, "Id");
            this.simpleRepository.update(origin);
            return origin;
        }
        return null;
    }

    private static Set<String> createIgnoreSet(String ... ignoresProperties ) {
        Set<String> ignores = new HashSet<>();
        if (ignoresProperties != null) {
            ignores.addAll(Arrays.asList(ignoresProperties));
        }
        return ignores;
    }

    private static void copyNotNullValue(Object data, Object origin, String ... ignoresProperties) {
      
        Set<String> ignores = createIgnoreSet(ignoresProperties);
        Method[] originSetters = origin.getClass().getMethods();
        
        try {
            for (Method method : originSetters) {
                String methodName = method.getName();
                if (methodName.startsWith("set")) {
                    String methodNameSuffix = methodName.substring(3);
                    if (!ignores.contains(methodNameSuffix)) {
                        Method getMethod = data.getClass().getMethod("get" + methodNameSuffix);
                        if (getMethod != null) {
                            Object value = getMethod.invoke(data);
                            if (value != null) {
                                method.invoke(origin, value);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error(GlobalConfig.LOG_EXCEPTION_PREFIX, e);
        }
    }

    /**
     *
     * @param type yijian | wenti | tianqi
     */
    @Override
    public List<TemplateInstances> findAllTemplateInstances(String type) {

        return this.simpleRepository.findAllTemplateInstances(type);
    }
}
